# 元组 是一个固定长度,不可改变的python序列对象.创建元组的最简单方式,是用逗号分隔-列值
from itertools import groupby

from click import clear
from sqlalchemy.orm.collections import collection

tup = 4, 5, 6  # (4, 5, 6)
# 创建复杂的元组,使用圆括号,将值放在圆括号中
nested_tup = (4, 5, 6), (7, 8)
# tuple 可以将任意序列或迭代器转换成元组
tup1 = tuple([4, 2, 0])
tup2 = tuple('string')
# 使用方括号访问元组中的元素
a = tup[0]
# 元组中存储的对象可能是可变对象,一旦创建了元组,元组中的对象就不能修改了
tup3 = tuple(['foo', [1, 2], True])
b = tup3[1]
# 如何元组中的某个对象是可变的,列表可以在原位进行修改
tup3[1].append(3)  # ('foo', [1, 2, 3], True)
# 串联元组 可以使用加号
tup4 = (4, None, 'foo') + (6, 0) + ('bar',)  # (4, None, 'foo', 6, 0, 'bar')
# 元组乘以一个整数,向列表一样,会将几个元组的复制串联起来,对象本身并没有被复制,只是引用了它
tup5 = ('foo', 'bar') * 4  # ('foo', 'bar', 'foo', 'bar', 'foo', 'bar', 'foo', 'bar')
# 拆分元组 将元组赋值给类似元组的变量,python会试图拆分等号右边的值
tup6 = (4, 5, 6)
e, d, f = tup6
# 替换
e, f = 1, 2
# 变量拆分常用来迭代元组或列表序列
# seq = [(1, 2, 3), (4, 5, 6), (7, 8, 9)]
# for a, b, c in seq:
#     print('a={},b={},c={}'.format(a, b, c))
# 更高级的元组拆分功能,允许从元组的开头 "摘取" 几个元素,使用了*rest 特殊语法 用在函数签名中以抓取任意长度列表的位置参数
values = 1, 2, 3, 4, 5
a, b, *rest = values
# rest的部分是想要舍弃的部分,rest的名字不重要,作为惯用写法 很多人会将不需要的变量使用下滑线;
c, d, *_ = values
# tuple方法 因为元组的大小和内容不能修改,它的实例方法都很轻量,其中很有用的就是count 适用于列表 可以统计某个值得出现的频率
a = (1, 2, 2, 2, 3, 4, 2)
# print(a.count(2))

# 列表 与元组对比,列表的长度可变,内容可以被修改 用方括号定义 或者是 list函数
a_list = [2, 3, 4, None]
# list 函数常用来在数据处理中实体化迭代器或生成器
gen = range(10)
# print(list(gen))

# 可以用 append在列表未尾添加元素
b_list = [None] * 10
b_list.append('dwarf')

# insert 在指定位置添加元素 插入的序号必须在0和列表长度之间
# 与append相比，insert耗费的计算量大，因为对后续元素的引用必须在内部迁移，以便为新元素提供空间。如果要在序列的头部和尾部插入元素，你可能需要使用collections.deque，一个双尾部队列。
# insert 的逆运算是 pop 移除并返回指定位置的元素
b_list.insert(0, 'knight')
b_list.pop(1)

# 可以使用 remove 去除某个值 remove会先寻找第一个值并除去
b_list.remove('knight')
# 在不考虑性能 使用append 和 remove 把python的列表当做完美的多重集 数据结构

# 用 in 可以检查列表是否包含某个值
# print('dwarf' in b_list)
# 否定 in 可以再加一个 not
# print('dwarf' not in b_list)
# 在列表中检查是否存在某个值远比字典和集合速度慢，因为Python是线性搜索列表中的值，但在字典和集合中，在同样的时间内还可以检查其它项（基于哈希表）。

# 串联和组合列表 用+号将两个列表串联起来
# print([4, None,'foo'] + [5, 6,(2,3)])
# 如果定义了一个列表 用extend方法,可以添加多个值
x = [4, None, 'foo']
x.extend([5, 6, (2, 3)])

# 通过加法将列表串联的计算量较大，因为要新建一个列表，并且要复制对象。用extend追加元素，尤其是到一个大列表中，更为可取。因此：
# everything = []
# for chunk in list_of_lists:
#     everything.extend(chunk)

# 可以使用 sort 函数将一个列表原地排序(不创建新的对象)
a1 = [1, 45, 7, 4, 5]
a1.sort(reverse=True)
# sort 有选项很好用 reverse=True 反转排序 key二级排序  可以按长度对字符串进行排序
b1 = ['saw', 'small', 'He', 'foxes', 'six']
b1.sort(key=len)

# 二分搜索和维护已排序的列表 bisect 模块支持二分查找 和向已排序的列表插入值
# bisect.bisect可以找到插入值后仍保证排序的位置,bisect.insort 是向这个位置插入值
# bisect 模块不会检查列表是否已排好序,进行检查的话会耗费大量计算。因此，对未排序的列表使用bisect不会产生错误，但结果不一定正确。
import bisect

c1 = [1, 2, 2, 2, 5, 4, 6, 5, 7]
# print(bisect.bisect(c1, 2))
# print(bisect.bisect(c1, 5))
# print(bisect.insort(c1, 6))

# 切片 可以选取大多数序列类型的一部分,切片的基本形式是在方括号中使用 start:stop
seq = [7, 2, 3, 7, 5, 6, 0, 1]
# print(seq[1:5])
# 切片可以被序列赋值,切片的起始元素是包括的,不包含结束元素,因此,结果中包含的元素个数是stop - start
# start或stop都可以被省略 省略之后 分别默认序列的开头和结尾
seq[3:4] = [6, 3]
# print(seq[:5])
# print(seq[3:])

# 负数表明从后向前切片
# print(seq[-4:])
# print(seq[-6:-2])

# hello!
# 0,1,2,3,4,5,6
# -6,-5,-4,-3,-2,-1
# 正向 string[2:4] 负向 string[-5:-2]

# 第二冒号后使用 step 可以隔一个取一个元素
# print(seq[::2])
# 颠倒列表和元组 使用-1
# print(seq[::-1])

# 序列函数 enumerate 函数迭代一个序列时,如果想跟踪当前项的序号,可以使用 enumerate 函数
# i = 0
# for value in collection:
#     print('{} {}'.format(i, value))
#     i += 1
# python 内建一个 enumerate 函数 可以返回 (i,value) 元组序列
# for i, value in enumerate(collection):
#     print('{} {}'.format(i, value))

# 当索引数据时,使用enumerate 的一个好方法是计算序列唯一的 dict映射的位置的值
some_list = ['foo', 'bar', 'baz']
mapping = {}
for i, v in enumerate(some_list):
    mapping[v] = i

# print(mapping)

# sorted 函数可以从任意序列的元素返回一个新的排好序的列表 不改变原序列
# print(sorted([7,1,2,6,0,3,2]))
# print(sorted('horse race'))

# zip 可以将多个列表,元组或其他序列成对组合成一个元组序列
seq1 = ['foo', 'bar', 'baz']
seq2 = ['one', 'two', 'three']
zipped = zip(seq1, seq2)
# print(list(zipped))

# zip可以处理任意多的序列,元素的个数取决于最短的序列
seq3 = [False, True]
# print(list(zip(seq1, seq2, seq3)))

# zip常见的用法是同时迭代多个序列,可能结合 enumerate 使用
# for i, (a, b) in enumerate(zip(seq1, seq2)):
#     print('{0}: {1}, {2}'.format(i, a, b))

# 给出一个被压缩的序列 zip可以被用来解压序列  可以当做把行的列表转换为列的列表
pitchers = [('Nolan', 'Ryan'), ('Roger', 'Clemens'), ('Schilling', 'Curt')]
first_names, last_names = zip(*pitchers)
# print(first_names)
# print(last_names)

# reversed函数 可以从后向前迭代一个序列
seq4 = list(reversed(range(10)))
# reversed 是一个生成器,只有实体化(即列表或for循环)之后才能创建翻转的序列

# 字典 是python最为重要的数据结构 更为常见的名字是哈希映射或关联数组 是键值对的大小可变集合,键值都是python对象
# 创建字典的方法是使用尖括号,用冒号分隔键和值
empty_dict = {}
d1 = {'a': 'some value', 'b': [1, 2, 3, 4]}
# 访问列表或元组中的元素一样,访问插入或设定字典中的元素
d1[7] = 'an integer'
# print(d1)

# 检查列表和元组是否包含某个值的方法, 检查字典中是否包含某个键
# print('a' in d1)

# 用del关键字 或 pop方法返回值的同时删除键 删除值
d1[5] = 'some value'
del d1[5]

# keys和values 是字典的键和值的迭代器方法
# 获取键
# print(list(d1.keys()))
# print(list(d1.values()))

# 用 update可以将一个字典与另一个融合 update方法是原地改变字典,因此传递给update的键的旧的值都会被舍弃
d1.update({'b': 'foo', 'c': 12})

# 用序列创建字典
# mapping = {}
# for key,value in zip(key_list, value_list):
#     mapping[key] = value

# 字典本质上是2元元组的集合 dict可以接受2元元组的列表
# mapping = dict(zip(range(5),reversed(range(5))))
# print(mapping)

# 默认值
# if key in some_dict:
#     value = some_dict[key]
# else:
#     value = default_value

# dict的方法get 和 pop可以取默认值进行返回,上面的if-else可以简化
# value = some_dict.get(key, default_value)

# get 默认会返回None 如果不存在键,pop 会抛出一个例外,关于设定值,常见的情况是字典的值是属于其他集合 如列表

words = ['apple', 'bat', 'bar', 'atom', 'book']

by_letter = {}

# for word in words:
#     letter = word[0]
#     if letter not in by_letter:
#         by_letter[letter] = [word]
#     else:
#         by_letter[letter].append(word)

# setdefault 方法可以设定一个键的值,如果键不存在,否则返回键的值
# for word in words:
#     letter = word[0]
#     by_letter.setdefault(letter, []).append(word)

# collections 中有defaultdict 可以简化上面的代码 传递类型或函数以生成每个位置的默认值
from collections import defaultdict

# by_letter = defaultdict(list)
# for word in words:
#     by_letter[word[0]].append(word)

# 有效的键类型 字典的值可以是任意Python对象，而键通常是不可变的标量类型（整数、浮点型、字符串）或元组（元组中的对象必须是不可变的）。这被称为“可哈希性”。可以用hash函数检测一个对象是否是可哈希的（可被用作字典的键）
# hash('string')
# hash((1, 2, (2, 3)))  # 可作为键的元组
# hash((1, 2, [2, 3]))  # 不可作为键的元组

# 列表当做键 一种方法是列表转化为元组,只要内部元素可以被哈希化,那么元组就可以作为键
# d = {}
# d[tuple([1, 2, 3])] = 5

# 集合是无序的不可重复的元素集合 可以当做字典但只有键没有值 可以用两种方式创建集合 通过 set函数 或 使用尖括号 set语句
# set([1, 2, 3, 1, 2, 3])
# {2,3,3,43}

# 集合支持合并，交集，差分和对称差等数学集合运算
a = {1, 2, 3, 4, 5}
b = {4, 5, 6, 7, 8}

# 合并是取两个集合中不重复的元素 可以用union方法 或者 | 运算符
# a.union(b)
# a | b

# 交集的元素包含在两个集合中,可以用 intersection 方法 或者 & 运算符
# a.intersection(b)
# a & b

# 列表集合和字典推导式 列表推导式 允许用户方便的从一个集合过滤元素形成列表 在传递参数的过程中还可以修改元素
# [expr for val in collection if condition]
# 等同于
# result = []
# for val in collection:
#     if condition:
#         result.append(expr)

# filter条件可以被忽略,只留下表达式就行
# 给定一个字符串列表 可以过滤出长度在2及以下的字符串,并将其转换成大写
strings = ['a', 'as', 'bat', 'car', 'dove', 'python']
[x.upper() for x in strings if len(x) > 2]

# 字典推导式
# dict_comp  = {key-expr : value-expr for value in collection if condition}
# 集合的推导式
# set_comp = {expr for value in collection if condition}

# 集合推导式 想要字符串的长度
unique_lengths = {len(x) for x in strings}
# map 函数 可以简化
set(map(len, strings))

# 创建一个字符串的查找映射表以确定它在列表中的位置
loc_mapping = {val: index for index, val in enumerate(strings)}


# 函数 是python中最重要的代码组织和复用手段 如果需要重复使用相同或非常类似的代码
# 函数使用 def 关键字 用return 关键字返回值
def my_function(x, y, z=1.5):
    if z > 1:
        return z * (x + y)
    else:
        return z / (x + y)


# 同时拥有多条return也是可以的,如果到达函数末尾时没有遇到任何一条return语句,则返回None
# 函数可以有一些位置参数positional 和 一些 关键字参数keyword 关键字参数通常用于指定默认值或可选参数
# 上面的函数中 x和y是位置参数,z是关键字参数
# 调用
# my_function(5,6,z=0.7)
# my_function(3.14,7,3.5)
# my_function(10,20)
# 函数参数的主要限制在于 关键字参数必须位于位置参数之后,如果有的话,可以在任何顺序指定关键字参数,也可以用关键字传递位置参数
# my_function(x=5, y=6, z=0.7)

# 命名空间 作用域 和 局部函数
# 函数可以访问两种不同作用域中的变量 全局global 和 局部local python有一种更科学的用于描述变量作用域的名称,即命名空间
# 任何在函数中赋值的变量默认都是被分配到局部命名空间中,局部命名空间是在函数被调用是创建的,函数参数会立即填入该命名空间
# 在函数执行完毕之后 局部命名空间会被销毁
# def func():
#     a = []
#     for i in range(5):
#         a.append(i)
# 调用func()之后 会创建出空列表a 然后添加5个元素 最后a会在该函数退出后被销毁
# a = []
# def func():
#     for i in range(5):
#         a.append(i)

# 如果可以在函数中对全局变量进行赋值操作,但是那些变量必须用global关键字声明成全局的
# a = None
# def bind_a_variable():
#     global a
#     a = []
#
# bind_a_variable()
# 建议不要频繁使用global关键字 因为全局变量一般是用于存放系统的某些状态的,如果使用很多,说明得要来点面向对象编程 即使用类
# python编程时最喜欢的一个功能是 函数可以返回多个值
# def f():
#     a = 1
#     b = 2
#     c = 3
#     return a, b, c

# 函数 其实只返回了一个对象也就是一个元组 最后该元组会被拆分到各个结果变量中
# return_value = f()
# return_value将会是一个含有3个返回值的三元元组 ,多值返回方式--返回字典
# def f():
#     a = 1
#     b = 2
#     c = 3
#     return {'a': a, 'b': b, 'c': c}

# python函数都是对象
states = ['   Alabama ', 'Georgia!', 'Georgia', 'georgia', 'FlOrIda', 'south   carolina##', 'West virginia?']
# 数据清理 去除空白符,删除各种标点符号,正确的大写格式 做法之一是使用内建的字符串方法和正则表达式 re模块
import re


# def clean_strings(states):
#     result = []
#     for value in states:
#         value = value.strip() # 去除首尾空白符
#         value = re.sub('[!#?]', '', value) # 去除标点符号
#         value = value.title() # 首字母大写
#         result.append(value)
#     return result

# 需要在一组给定字符串上执行的所有运算做成一个列表
def remove_punctuation(value):
    return re.sub('[!#?]', '', value)


clean_ops = [str.strip, remove_punctuation, str.title]


def clean_strings(strings, ops):
    result = []
    for value in strings:
        for function in ops:
            value = function(value)
        result.append(value)
    return result


# 匿名lambda函数 仅由单条语句组成,该语句的结果就是返回值 通过lambda关键字定义的 这个关键字没有别的函数,仅仅是说我们正在声明的是一个匿名函数
def short_function(x):
    return x * 2


# lambda x: x * 2
# 自定义运算给apply_to_list函数
def apply_to_list(some_list, f):
    return [f(x) for x in some_list]


ints = [1, 2, 3, 4, 5]
apply_to_list(ints, lambda x: x * 2)

# 假设有一组字符串,你想要根据各字符串不同字母的数量对其进行排序
strings = ['a', 'as', 'bat', 'car', 'dove', 'python']
# 可以传入一个 lambda函数到列表的sort方法中
strings.sort(key=lambda x: len(set(list(x))))


# 柯里化 部分参数应用  它指的是通过“部分参数应用”（partial argument application）从现有函数派生出新函数的技术。例如，假设我们有一个执行两数相加的简单函数：
def add_numbers(x, y):
    return x + y


# 通过这个函数,我们可以派生出一个新的只有一个参数的函数 add_five 用于对其参数加5
# add_five = lambda y: add_numbers(5, y)
# add_numbers的第二个参数称为柯里化的 因为我们其实就只是定义了一个可以调用现有函数的新函数而已
# 内置的 functools 模块提供了更方便的 partial 函数,它接受一个函数和一个或多个参数,返回一个新函数,这个新函数会调用原始函数,并且已经指定了部分参数值
from functools import partial

add_five = partial(add_numbers, 5)

# 生成器 yield 关键字 能以一种一致的方式对序列进行迭代 比如列表中的对象或文件中的行 迭代器协议是一种使对象可迭代的通用方式的方式实现的
# 一个原生的使对象可迭代的方法 对字典进行迭代可以得到其所有键
some_dict = {'a': 1, 'b': 2, 'c': 3}
for key in some_dict:
    print(key)

# for key in some_dict时,会尝试从some_dict创建一个迭代器,然后调用next方法获取字典中的下一个键
dict_iterator = iter(some_dict)

# 迭代器是一种特殊对象,可以在诸如for 循环之类的上下文中向python 解释器输送对象.大部分能接受列表之类的对象的方法也可以接受任何可迭代对象
# min max sum等内置方法以及list tuple等类型构造器
list(dict_iterator)


# 生成器 是构造新的可迭代对象的一种简单方式,一般的函数执行之后只会返回单个值,而生成器则是以延迟的方式返回一个值序列,即每返回一个值之后暂停,
# 知道下一个值被请求时再继续.要创建一个生成器,只要将函数中的return 替换为 yeild 语句
def squares(n=10):
    print('Generating squares from 1 to {0}'.format(n ** 2))
    for i in range(1, n + 1):
        yield i ** 2


# 调用该生成器 没有任何代码会被立即执行
gen = squares()

# 生成器表达式 是一种特殊的生成器表达式,它们和列表推导式一样,但是它们会返回一个生成器对象 其创建方式为 把列表推导式两端的方括号改为圆括号
gen1 = (x ** 2 for x in range(100))

# 生成器表达式可以取得列表推导式作为函数参数
sum(x ** 2 for x in range(100))
dict((i, i ** 2) for i in range(5))

# itertools模块 中有一组用于许多常见数据算法的生成器。例如，groupby可以接受任何序列和一个函数。它根据函数的返回值对序列中的连续元素进行分组。
import itertools

first_letter = lambda x: x[0]
names = ['Alan', 'Adam', 'Wes', 'Will', 'Sarah', 'Tara']
for letter, names in itertools.groupby(names, first_letter):
    print(letter, list(names))


# combination(iterable,k)  生成一个由iterable中所有可能的k元元组组成的序列,不考虑顺序
# permutations(iterable,k)  生成一个由iterable中所有可能的k元元组组成的序列,考虑顺序
# groupby(iterable[,keyfunc])  生成一个由iterable中所有可能的k元元组组成的序列,不考虑顺序 为每个唯一键生成一个(key,sub-iterator)

# 错误和异常处理
def attempt_float(x):
    try:
        return float(x)
    # except: 元组包含多个异常
    except (ValueError, TypeError):
        return x
    # 如果不想抑制异常,无论try部分是否成功 都会执行finally
    finally:
        f.close()


# 常用于文件处理 try...finally
# f = open(path,'w')
# try:
#     write_to_file(f,data)
# finally:
#     f.close()

# 文件读取 使用open函数以及一个相对或绝对的文件路径
# path = 'xxx'
# f = open(path)
# 默认情况下 文件是以只读模式 "r" 打开的 然后就可以像处理列表那样来处理这个文件句柄f
# for line in f:
# pass

# 从文件中取出行都带有完整的行结束符
# lines = [x.rstrip() for x in open(path)]  左侧去处行号
# open创建文件对象,一定要用close 关闭它 关闭文件可以返回操作系统资源  f.close()
# with语句可以更容易地清理打开的文件 在退出代码块时,自动关闭文件
# with open(path) as f:
#     lines = [x.rstrip() for x in f]

# f=open(path,'w') 就会有一个新文件被创建在 xx.txt 并覆盖掉该位置原来的任何数据
# x的文件模式,可以创建可写文件,如果文件路径存在就无法创建

# r   只读模式
# w   只写模式,创建新文件,删除同名的任何文件
# a   附加到现有文件 如果文件不存在则创建一个
# r+  读写模式
# b   附加说明某模式用于二进制文件 即 rb 或者是 wb
# u  通用换行模式,单独使用u或附加到其他读模式 如 ru
# 可读文件常用的方法是 read seek tell   read 会从文件返回字符,字符的内容是由文件的编码决定的
# f.read(10) read模式会将文件句柄的位置提前，提前的数量是读取的字节数。tell可以给出当前的位置
# seek将文件位置更改为文件中的指定字节：
# read([size])    以字符串形式返回文件数据，可选的size参数用于说明读取的字节数
# readlines([size])   将文件返回为行列表，可选参数size
# write(str)  将字符串写入文件
# close() 关闭句柄
# flush() 清空内部1/O缓存区，并将数据强行写回磁盘
# seek(pos) 移动到指定的文件位置（整数）
# tell()  以整数形式返回当前文件位置
# closed  如果文件已关闭，则为True



if __name__ == '__main__':
    a = clean_strings(states)
    print(a)
